#!/usr/bin/ruby
=begin

 Authors:		Wei Li <oldrev@gmail.com>
 Copyrights:	Copyright (C) 2007 by Wei Li
 License:		GPL

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

=end

require 'rexml/document'
require 'set'
require 'pathname'
require 'optparse'

=begin
TODO:
 * 调用约定
 * 产生多个D模块
 * 对齐
 * D2
=end

class SourceFile
	attr_accessor :name

	def initialize(name)
		@name = name
	end
end

class Parser
	XML_ROOT_ELEMENT = "GCC_XML"

	#C中不存在的D关键字 
	@@DKeywords = Set.new [ 
		"byte", "ubyte", "ushort", "uint", "ulong", "wchar", "dchar", "cent", "ucent",
		"real", "ifloat", "idouble", "ireal", "cfloat", "cdouble", "creal", 
		"ref", "in", "out", "inout", "throw", "try", "finally", "catch", "lzay",
		"macro", "alias", "private", "protected", "public", "package", "invariant", "scope", "final", "template",
		"override", "import", "mixin", "is", "with", "class", "this", "new", "delete", "align", 
		"unittest", "version", "debug", "assert", "__traits", "pragma", "super", "synchornized",
		"typeof", "typeid", "null", "module", "interface", "export", "delegate", "deprecated", "cast", 
		"abstract", "body", "bool", "foreach", "foreach_reverse", "function", "true", "false" # 还有吗？
	]

	attr_reader :main_file

	def initialize(xml_file, writer)
		@id_count = 0
		@writer = writer
		@xmlroot = REXML::Document.new(File.open(xml_file)).elements[XML_ROOT_ELEMENT]
		builitin_ele = @xmlroot.elements["File[contains(@name, 'gccxml_builtins.h')]"].attributes["id"]

		#去掉 GCCXML 内置的东西
		@xmlroot.elements.delete_all "*[@file='#{builitin_ele}']"

		@elements_cache = Hash.new
		@xmlroot.each_element { |node| @elements_cache[node.attributes["id"]] = node }

		f0_name = @xmlroot.elements["File[@id='f0']"].attributes["name"]
		@main_file = SourceFile.new(f0_name)
		
	end

	def generate_id
		@id_count += 1
		return "__HTD_gen_" + @id_count.to_s
	end

	def get_element_by_id(id)
		@elements_cache[id]
	end

	def get_name(ele)
		name = ele.attributes["name"]
		if not name then return nil end
		result = filter_keyword name #(mangled ? demangled : name)
		return result
	end

	def generate_linkage(func_ele)
		"extern(System)"
	end

	def generate_fundamental_type(type_ele)
		size = type_ele.attributes["size"].to_i
		case type_ele.attributes["name"]
		when "short int" :				"short"
		when "short unsigned int" :		"ushort"
		when "int" :					"int"
		when "long int" :				"int"
		when "unsigned int" :			"uint"
		when "long unsigned int" :		"int"
		when "long long int" :			"long"
		when "long long unsigned int" : "ulong"
		when "float" :					"float"
		when "double" :					"double"
		when "char" :					"char"
		when "unsigned char" :			"ubyte"
		when "signed char" :			"byte"
		when "void" :					"void"
		when "wchar_t" :				if size > 16 then "dchar" else "wchar" end
		when "signed wchar_t" :			if size > 16 then "int" else "short" end
		when "unsigned wchar_t" :		if size > 16 then "dchar" else "wchar" end
		end
	end

	def generate_array(e)
		length = e.attributes["max"].to_i + 1 - e.attributes["min"].to_i
		dcode = "[#{length.to_s}]"
	end

	def generate_type_chain(ele, chain) #生成类型链
		type = ele.attributes["type"]
		chain << ele
		if type then generate_type_chain(get_element_by_id(type), chain)  end
	end

	def generate_type(type_id, kind)
		#kind 有这几种： :Variable, :Parameter, :Return, :Field, :Typedef
		ele = get_element_by_id type_id
		chain = []
		generate_type_chain ele, chain

		dtype = []

		is_pointer = false
		is_array = false
		is_typedef = false
		is_const = false

		chain.reverse_each do |e| 
			case e.name 
			when "FundamentalType" then dtype << generate_fundamental_type(e)
			when "Enumeration" then dtype << get_name(e) 
			when "FunctionType" then dtype << generate_function_type(e)
			when "Struct" then  dtype << get_name(e) #嵌套的struct/union允许无名字段，但是D不允许
			when "Union" then dtype << get_name(e) 
			when "PointerType" then dtype << "*"; is_pointer = true
			when "ArrayType" then dtype << generate_array(e); is_array = true
			#这步忽略 Typedef and const
			when "Typedef" then is_typedef = true   # yeah，我们忽略所有 Typedef
			when "CvQualifiedType" then is_const = true #什么也不干 dtype << " const "
			else error "Unknown type '#{e.name}'"
			end
		end

		def is_atomic_type(aele)
			return aele.name == "FundamentalType" || aele.name == "Enumeration" ? true : false
		end
		
		#处理 const, D1 对 constness 的支持有限, 只支持在 :Variable 中使用
		if kind == :Variable then
			#情况1： const atomic_type 形式
			if chain.length == 2 && is_atomic_type(chain.last) &&
					chain[chain.length - 2].name == "CvQualifiedType" then
				dtype.insert 0, "const"
			end

			#情况2： const typedef atomic_type 形式
			if chain.length == 3 && is_atomic_type(chain.last) &&
					chain[chain.length - 3].name == "CvQualifiedType" then
				dtype.insert 0, "const"
			end
		end

		return dtype.join(" ")

	end

	def generate_parameters(func_ele)
		params = []

		func_ele.elements.each("Argument") do |arg| 
			type = generate_type(arg.attributes["type"], :Parameter)
			name = get_name arg
			params << if name then "#{type} #{name}" else "#{type}" end
		end

		if params.length == 1 then return params[0]
		else 
			return params.join(", ")
		end
	end

	def emit_function(e)
		linkage = generate_linkage(e)
		return_type = generate_type(e.attributes["returns"], :Return)
		function_name = get_name e
		parameters = generate_parameters(e)
		return "#{linkage} #{return_type} #{function_name}(#{parameters});\n"
	end

	def generate_function_type(e)
		return_type = generate_type(e.attributes["returns"], :Return)
		parameters = generate_parameters(e)
		return "#{return_type} function(#{parameters})"
	end

	def generate_members(members)

		#处理无名成员，为它们加上名字
		members.each do |mid|
			member_ele = get_element_by_id(mid)
			mname = member_ele.attributes["name"]
			if !mname or mname == "" then 
				member_ele.attributes["name"] = generate_id
			end
		end

		#处理成员
		dcode = ""
		members.each do |mid|
			member_ele = get_element_by_id(mid)
			dcode << case member_ele.name
			when "Field" then generate_field member_ele
			#由于已经在外部定义了 struct/union，在此就忽略内部的 struct/union 乐
			#when "Struct" then generate_aggregation_body member_ele
			#when "Union" then generate_aggregation_body member_ele
			else ""
			end
		end

		return dcode
	end

	def emit_aggregation(aggr_ele)
		generate_aggregation_body(aggr_ele)
	end

	def generate_aggregation_body(aggr_ele)
		aggr_name = get_name aggr_ele

		type_name = case aggr_ele.name
					when "Struct" : "struct"
					when "Union" : "union"
					else error "Invalid aggreation element '#{aggr_ele.name}'"
					end

		dcode = "\n#{type_name} #{aggr_name}\n"
		dcode << "{\n"
		members = aggr_ele.attributes["members"]
		if members and members.length > 0 then
			dcode << generate_members(members.split(" "))
		end
		dcode << "}\n\n"
		return dcode 
	end

	def emit_typedef(typedef_ele)
		typedef_name = get_name(typedef_ele)
		type = generate_type(typedef_ele.attributes["type"], :Typedef)
		dcode = "alias #{type} #{typedef_name};\n"
		return dcode
	end

	def generate_var_init(ele)
		init = ele.attributes["init"]
		if !init then return nil end
		init.sub!(/\{/, "[")
		init.sub!(/\}/, "]")
		chain = []
		generate_type_chain ele, chain

		#GCCXML 生成的多维数组初始化属性是错误地，所以我们简单地忽略它
		if chain.length > 2 then
			0.upto chain.length - 2 do |i|
				if chain[i].name == "ArrayType" and chain[i + 1].name == "ArrayType" then 
					return nil
				end
			end
		end

		#由于D中的有名Enum包含域，需要特殊处理
		if chain.last.name == "Enumeration" and 
			not chain.last.attributes["name"] =~ /\._\d+/ then
			return "#{get_name chain.last}.#{init}"
		end

		return init
	end

	def emit_variable(var_ele)
		var_name = get_name var_ele
		linkage = generate_linkage var_ele
		type = generate_type(var_ele.attributes["type"], :Variable)
		init = generate_var_init var_ele
		dcode = "#{type} #{var_name}"
		if var_ele.attributes["extern"] then dcode = "extern #{dcode}" end
		if init then dcode = "#{dcode} = #{init}" end
		dcode = "#{linkage} #{dcode};\n"
	end

	def generate_field(ele)
		name = get_name ele
		if not name then name = "" end
		type = generate_type(ele.attributes["type"], :Field)
		return "#{type} #{name};\n"
	end

	def emit_enum(enum_ele)	
		name = get_name enum_ele
		if name =~ /\._\d+/ then name = "" end
		dcode = "enum " << name << " : int \n"
		dcode << "{\n"
		members = []
		enum_ele.elements.each("EnumValue") do |ev|
			evname = get_name ev
			init = ev.attributes["init"]
			members << "\t#{evname} = #{init}"
		end

		if members.length <= 1
			dcode << members[0]
		else 
			dcode << members.join(",\n")
		end

		dcode << "\n}\n"
	end

	#防止C代码里的符号与D关键字冲突
	def filter_keyword(symbol)
		if @@DKeywords.include?(symbol) then symbol + "_" else symbol end
	end

	def parse_defines(filename) #解析 #define
		f = File.new(filename, "r")
		
		#C/C++ 中该死的 wchar_t 长度是不确定的，VC中是16位，GCC是32位，因此：
		#看看我们能否找到 wchar_t 类型
		wchar_t = "wchar" #默认是 wchar
		wce = @xmlroot.elements["FundamentalType[@name='wchar_t']"]
		if wce and wce.attributes["size"].to_i > 16 then 
			wchar_t = "dchar"
		end

		while line = f.gets

			#C中int类型可能由L结尾
			if line =~ /^\s*#define\s+(\w+)\s+\(?([-\+\.\w]+)L?\)?\s*$/ #数值型
				@writer << "const auto #{filter_keyword($1)} = #{$2};\n"
			elsif line =~ /^\s*#define\s+(\w+)\s+(\'.*\')\s*$/ then #ANSI 字符
				@writer << "const char #{filter_keyword($1)} = #{$2};\n"
			elsif line =~ /^\s*#define\s+(\w+)\s+(L\'.*\')\s*$/ then #wchar_t 字符
				@writer << "const #{wchar_t} #{filter_keyword($1)} = #{$2};\n"
			elsif line =~ /^\s*#define\s+(\w+)\s+(\".*\")\s*$/ then #ANSI 字符串
				@writer << "const char* #{filter_keyword($1)} = #{$2};\n"
			elsif line =~ /^\s*#define\s+(\w+)\s+(L\".*\")\s*$/ then #wchar_t 字符串
				@writer << "const #{wchar_t}* #{filter_keyword($1)} = #{$2};\n"
			end

		end
	end

	def parse_decl
		@xmlroot.each_element("Enumeration[@file='f0']")	{ |node| @writer << emit_enum(node) }
		@xmlroot.each_element("Struct[@file='f0']")			{ |node| @writer << emit_aggregation(node) }
		@xmlroot.each_element("Union[@file='f0']")			{ |node| @writer << emit_aggregation(node) }
		@xmlroot.each_element("Typedef[@file='f0']")		{ |node| @writer << emit_typedef(node) }
		@xmlroot.each_element("Function[@file='f0']")		{ |node| @writer << emit_function(node) }
		@xmlroot.each_element("Variable[@file='f0']")		{ |node| @writer << emit_variable(node) }
		#@xmlroot.each_element("File")		{ |node| puts node }
	end

	def parse
		parse_defines main_file.name
		parse_decl
	end

end #end of class Parser

def show_help
	show_logo
	puts "Usage:"
	puts "ruby htd.rb <options> <.h file>"
	puts "\nOptions:"
	puts "  -o DFILE\t\tname D header file to 'DFILE'"
	puts "  -g <options>\t\t<options> to GCCXML"
	puts "  -H/-?/--Help\t\tshow this help"
	puts
	exit 2
end

def show_logo
	puts "HTD 0.004 - A Binder for The D Programming Language"
	puts "Copyright (C) 2007 by Wei Li <oldrev@gmail.com>"
	puts "This is free software; see the source for copying conditions.  There is NO"
	puts "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
end

def error(msg)
	puts "Error：" << msg
	exit 1
end


d_filename = "generated.d"
gccxml_options = []

options = OptionParser.new
options.on("-?", "-H", "--Help") { |val| show_help }
options.on("-o [DFILE]", String) { |val| d_filename = val }
options.on("-g STRING", String) { |val| gccxml_options << val } 
args = options.parse(*ARGV)

h_filename = ""
if args.length > 1 then error "too many arguments" end
h_filename = args[0]

XmlFile = "output.xml"

DVersion2 = false

Cmd = "gccxml #{gccxml_options.join(" ")} #{h_filename} -fxml=#{XmlFile}"
gccxml_result = system Cmd

unless gccxml_result then error "Failed to execute gccxml" end


writer = File.new(d_filename, "w")
gen = Parser.new(XmlFile, writer)
gen.parse #解析并生成 D 文件
